﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace XForms.BaseComponents
{
    public class BaseForm : BaseUI
    {
        public List<BaseControl> ControlCollection { get; set; }

        public Int32 TitleBarSize { get; set; }

        public Boolean HasTitleBar { get; set; }

        public Color TitleColor { get; protected set; }

        public Texture2D TitleMaterial { get; protected set; }

        public Boolean IsFullscreen { get; protected set; }

        protected Rectangle TitleBar
        {
            get
            {
                return new Rectangle((Int32)this.Location.X, (Int32)this.Location.Y, this.Size.Width, this.TitleBarSize);
            }
        }

        public BaseForm(Game game, string name)
            : base(game, name)
        {
            //Setup Title bar.
            this.TitleBarSize = 20;
            this.HasTitleBar = true;
            this.SetTitleColor(Color.Red);
            this.Text = this.Name;
            this.IsTextVisible = true;
            this.Transparency = 0;

            //Instance our collection
            ControlCollection = new List<BaseControl>();

            //Event methods.
            this.OnCloseEvent += new OnCloseHandle(OnClose);
            this.OnHideEvent += new OnHideHandle(OnHide);
            this.OnShowEvent += new OnShowHandle(OnShow);
            this.OnUpdateEvent += new OnUpdateHandle(OnUpdate);
            this.OnDrawEvent += new OnDrawHandle(OnDraw);
            this.OnClickEvent += new OnClickHandle(OnClick);
            this.OnMoveEvent += new OnMoveHandler(OnMove);
            this.OnMouseOverEvent += new OnMouseOverHandle(OnMouseOver);

            this.SetFullScreen(false);
        }

        public override void Initialize()
        {
            base.Initialize();

            try
            {
                //Setup our size
                this.Size = new Size(this.Game.GraphicsDevice.Viewport.Width / 4, this.Game.GraphicsDevice.Viewport.Height / 4);
                this.Location = new Vector2(this.Game.GraphicsDevice.Viewport.Width / 2 - (this.Size.Width / 2), this.Game.GraphicsDevice.Viewport.Height / 2 - (this.Size.Height / 2));
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }

            foreach (BaseControl control in this.ControlCollection)
            {
                try
                {
                    control.Initialize();
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }
        }

        public virtual void SetFullScreen(Boolean isFullscreen)
        {
            if (isFullscreen)
            {
                this.IsStatic = true;
                this.Location = new Vector2(0, 0);
                this.Size = new Size(this.Game.GraphicsDevice.Viewport.Width, this.Game.GraphicsDevice.Viewport.Height);
                this.IsFullscreen = true;
            }
            else
            {
                this.Size = new Size(this.Game.GraphicsDevice.Viewport.Width / 4, this.Game.GraphicsDevice.Viewport.Height / 4);
                this.Location = new Vector2(this.Game.GraphicsDevice.Viewport.Width / 2 - (this.Size.Width / 2), this.Game.GraphicsDevice.Viewport.Height / 2 - (this.Size.Height / 2));
                this.IsFullscreen = false;
            }
        }

        /// <summary>
        /// Sets the Title color.
        /// </summary>
        /// <param name="color"></param>
        public virtual void SetTitleColor(Color color)
        {
            this.TitleColor = color;
            this.TitleMaterial = new Texture2D(this.Game.GraphicsDevice, 1, 1);
            this.TitleMaterial.SetData(new Color[] { color });
        }

        /// <summary>
        /// Sets the Title to the specified texture material.
        /// </summary>
        /// <param name="material"></param>
        public virtual void SetTitleMaterial(Texture2D material)
        {
            if (material == null) return;

            this.TitleMaterial = new Texture2D(this.Game.GraphicsDevice, material.Width, material.Height);
            this.TitleMaterial = material;
        }

        /// <summary>
        /// Sets the background to the specified texture material.
        /// </summary>
        /// <param name="material"></param>
        public virtual void SetTitleMaterial(string materialName)
        {
            if (String.IsNullOrEmpty(materialName))
                return;

            Texture2D material = this.Game.Content.Load<Texture2D>(materialName);
            this.TitleMaterial = new Texture2D(this.Game.GraphicsDevice, material.Width, material.Height);
            this.TitleMaterial = material;
        }

        public Boolean AddControl(BaseControl control)
        {
            //No null controls allowed.
            if (control == null)
                return false;

            //Check if the control has a valid name or not.
            if (String.IsNullOrEmpty(control.Name))
                return false;

            //Loop through each control in the collection
            //and see if we have a duplicate name
            foreach (BaseUI c in ControlCollection)
            {
                //Found a container with the same name, bail.
                if (c.Name == control.Name)
                    return false;
            }

            //No duplicates found so add to the collection
            ControlCollection.Add(control);
            control.SetOwner(this);
            return true;
        }

        /// <summary>
        /// Removes the specified Container and all of its Controls from the Screen.
        /// </summary>
        /// <param name="containerName">Name of the Container specified when it was instanced.</param>
        /// <returns></returns>
        public bool RemoveControl(string controlName)
        {
            BaseUI c = ControlCollection.First(item => item.Name == controlName);

            //If value IsNot null then return true.  Otherwise return false.
            bool value = c != null ? value = true : value = false;
            return value;
        }

        /// <summary>
        /// Returns a copy of the specified Container and all of its Controls, if it exists.
        /// </summary>
        /// <param name="containerName"></param>
        /// <returns></returns>
        public BaseUI GetControl(string controlName)
        {
            return ControlCollection.First(item => item.Name == controlName);
        }

        protected override void UpdateMousePosition(GameTime gameTime)
        {
            MouseState mouse = Mouse.GetState();

            if (!this.IsMoving)
            {
                Boolean mouseX, mouseY;

                if ((mouse.X >= this.Location.X) && (mouse.X <= this.Location.X + this.Size.Width))
                    mouseX = true;
                else
                    mouseX = false;
                if ((mouse.Y >= this.Location.Y) && (mouse.Y <= this.Location.Y + this.Size.Height))
                    mouseY = true;
                else
                    mouseY = false;

                if (mouseX && mouseY)
                    this.IsMouseOver = true;
                else
                    this.IsMouseOver = false;
            }
        }

        public override void Update(GameTime gameTime)
        {
            //No reason to execute the following code
            //if we are closed.  If we are just hidden, then update
            //since the Screen might still be accessed while hidden.
            if ((!this.Visible) && (!this.IsHidden))
                return;

            OnUpdateEvent(this, gameTime);

            //Itterate through each Container and call their update methods.
            foreach (BaseControl control in ControlCollection)
            {
                control.Update(gameTime);
            }

            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            OnDrawEvent(this, gameTime);

            base.Draw(gameTime);
        }

        public override void Show()
        {
            this.Visible = true;
            this.IsHidden = false;

            foreach (BaseControl control in this.ControlCollection)
            {
                control.Show();
            }
            OnShowEvent(this);
        }

        public override void Close()
        {
            //This Form can not be closed.
            if (this.CanClose)
                return;

            //Invoke the close event.
            bool isVisible = false;
            this.OnCloseEvent(this, out isVisible); //TODO: Test OnCloseEvent out parameter

            //if the user canceled the close event (such as 'Do you want to exit? Yes/No')
            //then we want to bail.
            if (isVisible)
                return;

            //Destroy each Container and their Controls stored on this Screen

            ControlCollection.ForEach(item => item.Close());
            /*
            for (int i = 0; i != ControlCollection.Count; i++)
            {
                //Invoke the destroy method for each control in this Container.
                ControlCollection[i].ForEach(item => item.OnDestroy(this));
                ControlCollection[i].Close();
            }
             */
            ControlCollection.Clear();

            this.Visible = isVisible;
        }

        public override void Hide()
        {
            this.IsHidden = true;
            this.OnHideEvent(this);
        }

        public delegate void OnShowHandle(BaseUI sender);
        public event OnShowHandle OnShowEvent;
        public override void OnShow(BaseUI sender)
        {
            //stub
        }

        public delegate void OnCloseHandle(BaseUI sender, out bool cancel);
        public event OnCloseHandle OnCloseEvent;
        public override void OnClose(BaseUI sender, out bool cancel)
        {
            throw new NotImplementedException();
        }

        public delegate void OnHideHandle(BaseUI sender);
        public event OnHideHandle OnHideEvent;
        public override void OnHide(BaseUI sender)
        {
            throw new NotImplementedException();
        }

        public delegate void OnClickHandle(BaseUI sender);
        public event OnClickHandle OnClickEvent;
        public override void OnClick(BaseUI sender)
        {
            if (!this.IsStatic)
            {
                MouseState mouse = Mouse.GetState();

                Vector2 currentPos = new Vector2(mouse.X, mouse.Y);
                if (Vector2.Distance(currentPos, this.MouseOrigin) > 1)
                {
                    this.IsMoving = true;
                    this.OnMoveEvent(this);
                }
                else
                    this.IsMoving = false;
            }
        }

        public delegate void OnMoveHandler(BaseUI sender);
        public event OnMoveHandler OnMoveEvent;
        public override void OnMove(BaseUI sender)
        {
            MouseState mouse = Mouse.GetState();

            Vector2 offset = new Vector2(this.MouseOrigin.X - this.ControlOrigin.X, this.MouseOrigin.Y - this.ControlOrigin.Y);
            Vector2 newLocation = new Vector2(mouse.X - offset.X, mouse.Y - offset.Y);

            //Reset the offset field so we can calc the offset
            //from the form to the control
            //Now move the Controls that this Form has with it.
            foreach (BaseControl control in this.ControlCollection)
            {
                offset = Vector2.Subtract(this.Location, control.Location);
                float x, y;
                x = newLocation.X + Math.Abs(offset.X);
                y = newLocation.Y + Math.Abs(offset.Y);
                control.Location = new Vector2(x, y);
            }

            this.Location = newLocation;
        }

        public delegate void OnMouseOverHandle(BaseUI sender);
        public event OnMouseOverHandle OnMouseOverEvent;
        public override void OnMouseOver(BaseUI sender)
        {

        }

        public delegate void OnUpdateHandle(BaseUI sender, GameTime gameTime);
        public event OnUpdateHandle OnUpdateEvent;
        public override void OnUpdate(BaseUI sender, GameTime gameTime)
        {
            MouseState mouse = Mouse.GetState();

            this.UpdateMousePosition(gameTime);

            if (this.IsMouseOver)
                this.OnMouseOverEvent(this);

            //Check to make sure that we are over the UI Component before firing
            //the OnClick event
            if (mouse.LeftButton == ButtonState.Pressed && this.IsMouseOver)
            {
                Boolean ignoreEvent = false;

                foreach (BaseControl control in this.ControlCollection)
                {
                    //We don't want to fire the event due to the user
                    //currently moving another control over the top of this.
                    if (control.IsMoving && control.Name != this.Name)
                    {
                        ignoreEvent = true;
                        break;
                    }
                }

                if (!ignoreEvent)
                {
                    //Capture the initial location of the mouse before we start moving
                    if (!this.IsMouseDown)
                    {
                        this.ControlOrigin = new Vector2(this.Location.X, this.Location.Y);
                        this.MouseOrigin = new Vector2(mouse.X, mouse.Y);
                    }
                    this.IsMouseDown = true;
                    this.OnClickEvent(this);
                }
            }
            else
            {
                this.IsMoving = false;
                this.IsMouseDown = false;
            }
        }

        public delegate void OnDrawHandle(BaseUI sender, GameTime gameTime);
        public event OnDrawHandle OnDrawEvent;
        public override void OnDraw(BaseUI sender, GameTime gameTime)
        {
            //If we are not visible we don't want to render.
            //Rrgardless if it's just 'hidden' but still running.
            if ((!this.Visible) || (this.IsHidden))
                return;

            this.SpriteBatch.Begin();

            this.SpriteBatch.Draw(this.BackgroundMaterial, this.Interface, null, Color.Lerp(Color.White, Color.Transparent, MathHelper.Clamp(this.Transparency / 100, 0, 1)), this.Rotation, new Vector2(), SpriteEffects.None, this.DrawOrder);

            //If we have a title bar specified, draw it.
            if (this.HasTitleBar)
                this.SpriteBatch.Draw(this.TitleMaterial, this.TitleBar, null, Color.Lerp(Color.White, Color.Transparent, MathHelper.Clamp(this.Transparency / 100, 0, 1)), this.Rotation, new Vector2(), SpriteEffects.None, this.DrawOrder);

            //If we have text specified and the following requirements are met:
            //1: Font is not NULL  2: Text is not NULL
            //Then render the text to the center of the Title Bar.
            //TODO: Provide Location Presets such as Left, Center, Right, etc.
            if (this.IsTextVisible && this.Font != null && !String.IsNullOrEmpty(this.Text))
            {
                int centerX = (this.TitleBar.X + this.TitleBar.Width / 2) - (int)(this.Font.MeasureString(Text).X / 2);
                Vector2 center = new Vector2(centerX, this.TitleBar.Y);

                this.SpriteBatch.DrawString(this.Font, this.Text, center, Color.White);
            }

            this.SpriteBatch.End();

            foreach (BaseControl container in this.ControlCollection)
            {
                container.Draw(gameTime);
            }
        }
    }
}
